package com.android.volley.codec;


import android.util.Log;

import com.android.volley.socket.IOUtil;
import com.android.volley.socket.SocketClient;

import org.json.JSONObject;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.security.KeyPair;

/**
 * Created by yinzhiqun on 2015/12/4.
 * 先用非对称加密获取秘钥，后续请求都用该秘钥进行加解密
 */
public class CodecFactory {

    private static YQKey key;
    private static Decoder decoder = new DefaultDecoder();
    private static Encoder encoder = new DefaultEncoder();

    public static Decoder getDecoder(Socket socket) {
//        checkSecretKey(socket);//一般都是先编码，这里不需要更新秘钥，也避免了请求和响应间隔期间，秘钥过期问题
        return decoder;
    }

    public static Encoder getEncoder(Socket socket) throws SocketTimeoutException {
        checkSecretKey(socket);
        return encoder;
    }

    private static void checkSecretKey(Socket socket) throws SocketTimeoutException {
        if (key == null || key.isTimeout()) {
            synchronized (CodecFactory.class) {
                if (key == null || key.isTimeout()) {

                    try {
                        YQKey YQSecretKey = generateKey(socket);
                        encoder = new YQEncoder(YQSecretKey);
                        decoder = new YQDecoder(YQSecretKey);
                        key = YQSecretKey;
                    }catch (SocketTimeoutException e){
                        throw e;
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private static YQKey generateKey(Socket socket) throws Exception {
        //尝试从缓存获取key
        YQKey keyFromFile = YQKey.deSerializeFromFile(new File(SocketClient.filesDir+"/key"));
        if (keyFromFile!=null && !keyFromFile.isTimeout()){
            return keyFromFile;
        }

        KeyPair keyPair = RSACoder.initKey();
        String publicKeyStr = RSACoder.encryptBASE64(keyPair.getPublic().getEncoded());
        String requestStr = "{\"cmd\":\"getkey\",\"pubkey\":\"" + string2Json(publicKeyStr) + "\",\"os\":\"android\"}";
//        String requestStr = "{\"cmd\":\"getkey\",\"pubkey\":\"" + string2Json(publicKeyStr) + "\"}";

        Log.w("kids", "更新秘钥, " + requestStr);

        byte[] jsonBuffer = requestStr.getBytes();
        OutputStream out = socket.getOutputStream();
        IOUtil.writeInt(out, jsonBuffer.length);
        out.write(jsonBuffer);

        InputStream in = socket.getInputStream();
        int totalLength = IOUtil.readInt(in);
        byte[] read = IOUtil.read(in, totalLength);

        YQKey keyFromServer = parseResponse(read, keyPair.getPrivate().getEncoded());
        keyFromServer.serializeToFile(new File(SocketClient.filesDir+"/key"));//缓存key
        return keyFromServer;
    }


    private static YQKey parseResponse(byte[] bytes,byte[] privateKey) throws Exception {
        String response = new String(bytes);
//        Log.w("kids", "更新秘钥2, " + response);

        JSONObject result = new JSONObject(response).getJSONObject("result");//如果数据异常，get会抛出，opt会返回null
        String appid = result.getString("appid");
        String secretkey = result.getString("secretkey");
        long expires = result.getLong("expires");

        byte[] desKey = RSACoder.decryptByPrivateKey(RSACoder.decryptBASE64(secretkey), privateKey);

//        StringBuilder stringBuilder = new StringBuilder();
//        for (byte b : desKey) {
//            stringBuilder.append(Integer.toHexString(b)).append(" ");
//        }
//        Log.w("kids", "更新秘钥3, " + stringBuilder.toString());

        return new YQKey().setAppid(appid).setExpires(expires).setSecretKey(desKey);
    }

    /*
     * From RFC 4627, "All Unicode characters may be placed within the
     * quotation marks except for the characters that must be escaped:
     * quotation mark, reverse solidus, and the control characters
     * (U+0000 through U+001F)."
     */
    static String string2Json(String value) {
        if (value==null||"".equals(value)){
            return "";
        }
        StringBuilder stringBuilder = new StringBuilder();
        char[] chars = value.toCharArray();

        for (int i = 0, length = chars.length; i < length; i++) {
            char c = chars[i];

            switch (c) {
                case '"':
                case '\\':
                case '/':
                    stringBuilder.append('\\').append(c);
                    break;

                case '\t':
                    stringBuilder.append("\\t");
                    break;

                case '\b':
                    stringBuilder.append("\\b");
                    break;

                case '\n':
                    stringBuilder.append("\\n");
                    break;

                case '\r':
                    stringBuilder.append("\\r");
                    break;

                case '\f':
                    stringBuilder.append("\\f");
                    break;

                default:
                    if (c <= 0x1F) {
                        stringBuilder.append(String.format("\\u%04x", (int) c));
                    } else {
                        stringBuilder.append(c);
                    }
                    break;
            }
        }

        return stringBuilder.toString();
    }
}
